package com.summer.media.videoProcessJob;

import com.summer.media.mapper.MediaProcessMapper;
import com.summer.media.pojo.MediaProcess;
import com.summer.media.service.MediaFileService;
import com.summer.media.service.MediaProcessService;
import com.summer.utils.Mp4VideoUtil;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.*;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;


@Component
@Slf4j
public class VideoProcessJob {

    @Autowired
    private MediaProcessService mediaProcessService;

    @Autowired
    private MediaProcessMapper mediaProcessMapper;

    @Autowired
    private MediaFileService mediaFileService;

    @Value("${videoprocess.ffmpegpath}")
    private String ffmpegpath;

    /**
     * 分片广播任务
     */
    @XxlJob("videoJobHandler")
    public void videoJobHandler() {
        System.out.println("--------------------开始执行任务-------------------------");
        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();

        // 1. 根据cpu线程数查询本分片获取到的任务，多线程处理
        int processors = Runtime.getRuntime().availableProcessors(); // 获取此电脑线程数
        List<MediaProcess> taskList = mediaProcessService.getTask(shardTotal, shardIndex, processors);
        int size = taskList.size();
        if (size <=0){
            log.error("任务数量为0, shardIndex = {}, shardTotal = {}, CPU线程数为：{}", shardIndex, shardTotal, processors);
            return;
        }
        log.info("shardIndex = {}, shardTotal = {}, CPU线程数为：{}", shardIndex, shardTotal, processors);

        //启动size个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(size);

        //计数器, 等待全部线程执行完才结束方法，防止执行时间过长，新任务就来调度了
        CountDownLatch countDownLatch = new CountDownLatch(size);

        for (MediaProcess mediaProcess : taskList) {
            threadPool.execute(() -> {
                try {
                    Long id = mediaProcess.getId();
                    String bucket = mediaProcess.getBucket();
                    String objectName = mediaProcess.getFilePath();
                    String filename = mediaProcess.getFilename();
                    // 2. 抢分布式锁，若抢不到则更新media_process表中错误次数与信息，打印错误日志并return
                    boolean isGetLock = mediaProcessService.getLock(id);
                    if (!isGetLock) {
                        log.error("失败信息：shardIndex = {}, shardTotal = {}, CPU线程数为：{}", shardIndex, shardTotal, processors);
                        log.error("获取分布式锁失败，mediaProcess={}", mediaProcess);
                        mediaProcess.setErrormsg("获取分布式锁失败");
                        updateErrorMsg(mediaProcess);
                        return;
                    }
                    log.info("分布式锁获取成功...");

                    // 3. 此时抢到分布式锁了，从minio下载视频到本地服务器（临时文件）
                    File localTemp = mediaFileService.downloadFileFromMinIOToLocalTemp(bucket, objectName);
                    if (localTemp == null) {
                        log.error("失败信息：shardIndex = {}, shardTotal = {}, CPU线程数为：{}", shardIndex, shardTotal, processors);
                        log.error("从minio下载视频失败，mediaProcess={}", mediaProcess);
                        mediaProcess.setErrormsg("从minio下载视频失败");
                        updateErrorMsg(mediaProcess);
                        return;
                    }
                    log.info("从minio下载原视频成功,保存位置localTemp = {}", localTemp);

                    // 4. 调用视频转码api处理视频
                    File tempProcessed = null;
                    String video_path = null;
                    String mp4_name = null;
                    String mp4folder_path = null;

                    Mp4VideoUtil mp4VideoUtil = null;
                    String isSuccess = null;
                    try {
                        tempProcessed = File.createTempFile("minio", ".mp4"); // 必须有“点” .mp4
                        tempProcessed.deleteOnExit();
                        // 创建视频转码需要的参数
                        video_path = localTemp.getAbsolutePath();
                        mp4_name = tempProcessed.getName();
                        mp4folder_path = tempProcessed.getAbsolutePath();
                        log.info("开始视频转码，video_path ={}，mp4_name ={}，mp4folder_path = {}",video_path, mp4_name, mp4folder_path);
                        mp4VideoUtil = new Mp4VideoUtil(ffmpegpath, video_path, mp4_name, mp4folder_path);
                        isSuccess = mp4VideoUtil.generateMp4();
                    } catch (Exception e) {
                        log.error("失败信息：{}", e.getMessage());
                        log.error("失败任务：id = {}, file_name = {}", id, filename);
                        log.error("视频转码失败，mediaProcess={}", mediaProcess);
                        mediaProcess.setErrormsg("视频转码失败");
                        updateErrorMsg(mediaProcess);
                        e.printStackTrace();
                        return;
                    }
                    log.info("------isSuccess = {}",isSuccess);
                    log.info("视频转码成功,保存位置tempProcessed = {}", mp4_name);

                    // 5. 将处理完的视频上传到minio
                    String processedMD5 = getFileMd5(tempProcessed);
                    if (processedMD5 ==null){
                        log.error("获取转码完成的视频MD5值失败");
                        mediaProcess.setErrormsg("上传视频到minio失败");
                        updateErrorMsg(mediaProcess);
                        return;
                    }
                    String newObjectName = getVideoSaveFolder(processedMD5) + processedMD5 + ".mp4";
                    log.info("开始上传视频到minio，newObjectName = {}", newObjectName);
                    boolean isUploadSuccess = mediaFileService.upload2minio(bucket,
                            tempProcessed.getAbsolutePath(),
                            "video/mp4",
                            newObjectName);
                    if (!isUploadSuccess) {
                        log.error("失败信息：shardIndex = {}, shardTotal = {}, CPU线程数为：{}", shardIndex, shardTotal, processors);
                        log.error("上传视频到minio失败，mediaProcess={}", mediaProcess);
                        mediaProcess.setErrormsg("上传视频到minio失败");
                        updateErrorMsg(mediaProcess);
                        return;
                    }
                    if (!isSuccess.equals("ffmpeg version N-90173-gfa0c9d69d3 Copyright (c) 2000-2018 the FFmpeg developers")){
                        log.warn("视频时长不一致，源视频objectName = {}, 新视频newObjectName = {}", objectName,newObjectName);
                    }
                    log.info("上传视频到minion成功,源视频objectName = {}, 新视频newObjectName = {}", objectName,newObjectName);

                    // 6. 保存处理结果：更新三张表记录（file、process、history）
                    String url = "/" + bucket + "/" + newObjectName;
                    boolean result = mediaProcessService.saveResult(mediaProcess, newObjectName, url);
                    if (!result) {
                        log.error("失败信息：shardIndex = {}, shardTotal = {}, CPU线程数为：{}", shardIndex, shardTotal, processors);
                        log.error("保存处理结果失败，mediaProcess={}", mediaProcess);
                        mediaProcess.setErrormsg("保存处理结果失败");
                        updateErrorMsg(mediaProcess);
                    }
                    log.info("保存结果成功！");
                }finally {
                    countDownLatch.countDown();
                }
            });
        }
        // [6]. 删除DFS中源格式视频，节省空间

        // 6. 阻塞方法，等待全部线程执行完毕方法结束。countDownLatch减为0时或方法执行30分中后才放行
        try {
            countDownLatch.await(30, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    // 视频处理失败时更新错误次数与错误信息
    private void updateErrorMsg(MediaProcess mediaProcess){
        mediaProcess.setStatus("3");
        mediaProcess.setFailCount(mediaProcess.getFailCount() + 1);
        int i = mediaProcessMapper.updateById(mediaProcess);
        if (i <= 0){
            log.error("更新错误信息失败，mediaProcess = {}", mediaProcess);
        }
    }

    // 获取视频文件MD5
    private String getFileMd5(File file) {
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            return DigestUtils.md5Hex(fileInputStream);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // 根据MD5获取文件分块目录： a/b/ab..../chunk/
    private String getVideoSaveFolder(String fileMd5){
        return fileMd5.charAt(0) + "/" + fileMd5.charAt(1) + "/" + fileMd5 + "/";
    }

}
